/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.chl.angholt.bookproject.db;

import edu.chl.angholt.bookproject.db.entity.Book;
import edu.chl.angholt.bookproject.db.entity.Read;
import edu.chl.angholt.bookproject.db.entity.Read_;
import edu.chl.angholt.bookproject.db.entity.User;
import edu.chl.angholt.bookproject.db.entity.User_;
import edu.chl.angholt.bookproject.db.exception.NonexistentEntityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

/**
 *
 * @author Jonas
 * Controller class for the user entity
 */
public class UserJpaCtrl extends EntityJpaCtrl<User> {

    public UserJpaCtrl(EntityManagerFactory emf) {
        super(emf);
    }

    /**
     * Get a list of all books that the user has read
     * @param user
     * @return List of books that the user has read
     */
    public List<Book> getReadBooks(User user) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Read> cq = builder.createQuery(Read.class);

        Root<Read> read = cq.from(Read.class);

        Join<Read, User> readUserJoin = read.join(Read_.user);

        cq.where(builder.equal(readUserJoin.get(User_.userId), user.getUserId()));

        TypedQuery<Read> query = em.createQuery(cq);

        List<Read> readlist =
                Collections.checkedList(query.getResultList(), Read.class);
        ArrayList<Book> books = new ArrayList<Book>(readlist.size());
        em.close();
        for (Read r : readlist) {
            books.add(r.getRead());
        }

        return books;
    }

    /**
     * Remove a user from the database
     * @param id The Id of the user to remove
     * @throws NonexistentEntityException If no user with that Id can be found
     */
    @Override
    public void destroy(Long id) throws NonexistentEntityException {
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();


        tx.begin();
        User u = em.find(User.class, id);

        if (u == null) {
            throw new NonexistentEntityException("Can not remove entity because it does not exist");
        }


        em.remove(u);
        tx.commit();

        em.close();
    }

    /**
     * Find the specified user
     * @param id The id of the user to find
     * @return The found user
     */
    @Override
    public User findEntity(Long id) {
        EntityManager em = emf.createEntityManager();
        return em.find(User.class, id);
    }

    /**
     * Finds all users in the database
     * @return A list of all users in the database
     */
    @Override
    public List<User> findEntities() {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> u = query.from(User.class);

        query.select(u);

        TypedQuery<User> tq = em.createQuery(query);
        List<User> users = tq.getResultList();
        em.close();
        return users;
    }

    /**
     * Finds a subset of users from the database.
     * @param maxResults The maximum number of users to find
     * @param firstResult The position of the first user to find
     * @return A list of the found users
     */
    @Override
    public List<User> findEntities(int maxResults, int firstResult) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> u = query.from(User.class);

        query.select(u);

        TypedQuery<User> tq = em.createQuery(query);
        tq.setFirstResult(firstResult);
        tq.setMaxResults(maxResults);
        List<User> users = tq.getResultList();
        em.close();
        return users;
    }

    /**
     * 
     * @return The number of users in the database
     */
    @Override
    public int getEntityCount() {
        return findEntities().size();
    }

    /**
     * Checks if the specified username (email-address) exists.
     * @param name The username to check for.
     * @return True if the user exists else false.
     */
    public boolean doesEmailExist(String email) {
        EntityManager em = emf.createEntityManager();

        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> u = query.from(User.class);

        query.select(u).where(builder.equal(u.get(User_.email), email));
        TypedQuery<User> tq = em.createQuery(query);
        List<User> users = tq.getResultList();
        em.close();
        if (users.isEmpty()) {
            return false;
        } else {
            return true;
        }


    }

    /**
     * Retrieves the user with the specified name
     * @param name The name of the user to get
     * @return The user with the specified name
     */
    public User getUser(String name) {
        EntityManager em = emf.createEntityManager();
       
        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> u = query.from(User.class);

        query.select(u).where(builder.equal(u.get(User_.email), name));

        TypedQuery<User> tq = em.createQuery(query);
        User user = tq.getSingleResult();
        em.close();
        return user;
    }

    /**
     * Add a book to the users list of read books
     * @param user The user to whose list to add the book
     * @param book The book to add
     */
    public void addReadBook(User user, Book book) {
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        Read r = new Read(user, book, "read");

        tx.begin();
        em.persist(r);
        tx.commit();
        em.close();
    }

    /**
     * Check if the user has read the specified book
     * @param user
     * @param book
     * @return 
     */
    public boolean haveRead(User user, Book book) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<Read> query = builder.createQuery(Read.class);
        Root<Read> r = query.from(Read.class);
        Predicate pre = builder.and(builder.and(builder.equal(r.get(Read_.user), user), builder.equal(r.get(Read_.read), book), builder.equal(r.get(Read_.tabelName), "read")));
        query.select(r).where(pre);
        TypedQuery<Read> tq = em.createQuery(query);
        List<Read> reads = tq.getResultList();
        em.close();
        if (reads.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Remove a book from a users list of read books
     * @param user
     * @param book 
     */
    public void removeReadBook(User user, Book book) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<Read> query = builder.createQuery(Read.class);
        Root<Read> r = query.from(Read.class);
        Predicate pre = builder.and(builder.and(builder.equal(r.get(Read_.user), user), builder.equal(r.get(Read_.read), book), builder.equal(r.get(Read_.tabelName), "read")));
        query.select(r).where(pre);
        TypedQuery<Read> tq = em.createQuery(query);
        List<Read> reads = tq.getResultList();
        if (!reads.isEmpty()) {
            Read read = reads.get(0);
            EntityTransaction tx = em.getTransaction();
            tx.begin();
            em.remove(read);
            tx.commit();
            em.close();
        }
    }
}
